<?php
namespace Swiftx\Component\Orm\Statement;
use PDO;
use Dwoo\Core;
use PDOStatement;
use Dwoo\Template\Str;
use Swiftx\Component\Orm\Exception\Runtime;
use Swiftx\Component\Orm\Exceptions\Error;
use Swiftx\Interfaces\Orm\Pager as InterfacePager;
use Swiftx\Interfaces\Orm\Entity;
use Swiftx\Interfaces\Orm\Manager;
use Swiftx\Interfaces\Orm\Query as InterfaceQuery;

/**
 * 属性抽象
 * @package Swiftx\Component\Orm\Reflection
 */
class Query implements InterfaceQuery {

    /**
     * 查询名称
     * @var string
     */
    protected $name;

    /**
     * SQL语句
     * @var string
     */
    protected $sql;

    /**
     * 实体类名
     * @var string
     */
    protected $entityClass;

    /**
     * 严格模式
     * @var bool
     */
    protected $strict = false;

    /**
     * 数据项
     * @var array
     */
    protected $data = [];

    /**
     * 管理对象
     * @var Manager
     */
    protected $manager;

    /**
     * 连接实例
     * @var PDO
     */
    protected $connect;

    /**
     * 命名空间
     * @var string
     */
    protected $namespace;

    /**
     * 命名引用
     * @var string[]
     */
    protected $uses;

    /**
     * 编译器
     * @var Core
     */
    protected $builder;

    /**
     * 构造函数
     * @param Manager $manager
     * @param string $namespace
     * @param string[] $uses
     */
    public function __construct(Manager $manager, string $namespace, array $uses=[]){
        $this->manager = $manager;
        $this->namespace = $namespace;
        $this->uses = $uses;
        $self = $this;
        $this->builder = new Core();
        $this->builder->addPlugin('table', function (Core $core, string $class) use ($self){
            $class = $this->analysisClassName($class);
            $result = $self->manager->translateTableName($class);
            if($self->strict) $result = '`'.$result.'`';
            return $result;
        });
        $this->builder->addPlugin('field', function (Core $core, string $class, string $prototype) use ($self){
            $class = $this->analysisClassName($class);
            $result = $self->manager->translateTableField($class,$prototype);
            if(!$self->strict) return $result;
            $table = $self->manager->translateTableName($class);
            return '`'.$table.'`.`'.$result.'`';
        });
    }

    /**
     * 解析类名
     * @param string $class
     * @return string
     */
    protected function analysisClassName(string $class){
        if($class[0] == '\\') return $class;
        $classPrefix = explode('\\', $class)[0];
        if(isset($this->uses[$classPrefix]))
            return $this->uses[$classPrefix].'\\'.$class;
        return $this->namespace.'\\'.$class;
    }

    /**
     * 设置SQL语句
     * @param string $sql
     */
    public function setSql(string $sql){
        $this->sql = trim($sql);
    }

    /**
     * 设置查询名称
     * @param string $name
     */
    public function setName(string $name){
        $this->name = $name;
    }

    /**
     * 查看名称
     * @return string
     */
    public function getName():string {
        return $this->name;
    }

    /**
     * 设置严格模式
     * @param string $mode
     */
    public function setStrict(string $mode){
        $this->strict = strtolower($mode) == 'true';
    }

    /**
     * 设置连接实例
     * @param PDO $connect
     */
    public function setConnect(PDO $connect){
        $this->connect = $connect;
    }

    /**
     * 设置实体类名
     * @param string $className
     */
    public function setEntity(string $className){
        $this->entityClass = $className;
    }

    /**
     * 设置缓存目录
     * @param string $path
     */
    public function setRuntimeDir(string $path){
        $this->builder->setCompileDir($path.'/build');
        $this->builder->setCacheDir($path.'/cache');
    }

    /**
     * 编译字符串
     * @return string
     */
    protected function buildSql():string {
        $sql = new Str($this->sql);
        return trim($this->builder->get($sql, $this->data));
    }

    /**
     * 构造PDO结果集
     * @param string $sql
     * @return PDOStatement
     * @throws Error
     */
    protected function buildStatement(string $sql):PDOStatement{
        $params = [];
        preg_match_all('/:([^\s()]+)\s?/',$sql, $params);
        $params = $params[1];
        $statement = $this->connect->prepare($sql);
        foreach ($params as $param){
            $value = $this->data[$param];
            $name = ':'.$param;
            if(is_bool($value))
                $type = PDO::PARAM_BOOL;
            else if (is_int($value))
                $type = PDO::PARAM_INT;
            else if(is_null($value))
                $type = PDO::PARAM_NULL;
            else $type = PDO::PARAM_STR;
            $statement->bindValue($name, $value, $type);
        }
        if($statement->execute() === false)
            throw new Error('SQL 出错：'.$sql.':'.$statement->errorInfo()[2]);
        return $statement;
    }

    /**
     * 绑定查询参数
     * @param string $key
     * @param $value
     */
    public function bind(string $key, $value){
        $this->data[$key] = $value;
    }

    /**
     * 获取查询对象
     * @param int $offset
     * @return Entity
     * @throws Runtime
     */
    public function fetch(int $offset = 0):Entity {
        $result = $this->list(1, $offset);
        if(empty($result))
            throw new Runtime('对象不存在');
        return $result[0];
    }

    /**
     * 获取查询结果集
     * @param int $number
     * @param int $offset
     * @return array|Entity[]
     */
    public function list(int $number = 0, int $offset = 0):array {
        $sql = $this->buildSql();
        if($number != 0)
            $sql .= ' LIMIT '.$offset.','.$number;
        $statement = $this->buildStatement($sql);
        $result = [];
        while ($data = $statement->fetch(PDO::FETCH_ASSOC)) {
            $entity = $this->manager->buildEntity($this->entityClass, $data);
            $entity->setLinked(true);
            $result[] = $entity;
        }
        return $result;
    }

    /**
     * 统计查询记录数
     * @return int
     */
    public function count(): int {
        $sql = $sql = $this->buildSql();
        $sql = /** @lang */'SELECT COUNT(*) FROM ('. $sql.') AS TEMP';
        $statement = $this->buildStatement($sql);
        return (int)$statement->fetch()[0];
    }

    /**
     * 获取图片集合
     * @param int $current
     * @param int $number
     * @param bool $sync
     * @return InterfacePager
     */
    public function page(int $current, int $number, bool $sync=true):InterfacePager {
        $page = new Pager();
        $page->setCurrent($current);
        $page->setNumber($number);
        $currentQuery = $this;
        $page->setCounter(function () use ($currentQuery){
            return $currentQuery->count();
        });
        $page->setLoader(function ($start, $number) use ($currentQuery){
            return $currentQuery->list($number, $start);
        });
        if($sync) $page->runSync();
        return $page;
    }

    /**
     * 调试信息输出
     * @return array
     */
    public function __debugInfo(){
        $result['name'] = $this->name;
        $result['sql'] = $this->sql;
        return $result;
    }

}